package drds.plus.datasource.configuration;

import com.alibaba.druid.pool.DruidDataSource;
import drds.plus.common.lifecycle.AbstractLifecycle;
import drds.plus.common.lifecycle.Lifecycle;
import drds.plus.datasource.api.DataSourceWrapper;
import drds.tools.$;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;

import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 数据库动态切换的Handle类，所有数据库的动态切换 都是由这个类完成
 */
@Slf4j
public class DatasourceHandler extends AbstractLifecycle implements Lifecycle {
    public final static String DEFAULT_DRUID_MYSQL_VALIDATION_QUERY = "query 'x'";
    public String applicationId;
    public String datasourceId;
    private static AtomicInteger version = new AtomicInteger(1);
    /**
     * 数据源操作锁，当需要对数据源进行重建或者刷新时需要先获得该锁
     */
    public final ReentrantLock lock = new ReentrantLock();
    /**
     * 运行时配置
     */
    public volatile DatasourceConfiguration runTimeDatasourceConfiguration = new DatasourceConfiguration();

    /**
     * druid数据源通过init初始化
     */
    public volatile DruidDataSource druidDataSource;
    /**
     * Datasource 的包装类
     */
    private volatile DataSourceWrapper dataSourceWrapper = null;
    /**
     * 数据库状态改变回调
     */
    private volatile List<DatabaseStatusListener> databaseStatusListenerList;

    /**
     * 将TAtomDsConfDO转换成LocalTxDataSourceDO
     */
    public static DruidDataSource createDruidDataSource(String datasourceId, DatasourceConfiguration datasourceConfiguration) {
        try {
            DruidDataSource druidDataSource = new DruidDataSource();
            // 一下三个是druid监控需要的特殊配置
            // 针对名字加个version，避免动态推送时创建mbean失败
            druidDataSource.setName(datasourceId + "_" + version.getAndIncrement());
            druidDataSource.setTestOnBorrow(false);
            druidDataSource.setTestWhileIdle(true);
            druidDataSource.setUsername(datasourceConfiguration.getUsername());
            druidDataSource.setPassword(datasourceConfiguration.getPassword());
            // 根据数据库类型设置conURL和setConnectionProperties
            {
                String url = UrlTools.getUrl(datasourceConfiguration.getIp(), datasourceConfiguration.getPort(), datasourceConfiguration.getDatabaseName(), datasourceConfiguration.getConnectionProperties());
                druidDataSource.setUrl(url);
                druidDataSource.setValidationQuery(DEFAULT_DRUID_MYSQL_VALIDATION_QUERY);
            }
            // lazy init 先设置为0 后续真正执行时才创建连接
            druidDataSource.setInitialSize(datasourceConfiguration.getInitPoolSize());
            druidDataSource.setMinIdle(datasourceConfiguration.getMinPoolSize());
            druidDataSource.setMaxActive(datasourceConfiguration.getMaxPoolSize());
            if (datasourceConfiguration.getIdleTimeout() > 0) {
                druidDataSource.setTimeBetweenEvictionRunsMillis(datasourceConfiguration.getIdleTimeout() * 60 * 1000);
                druidDataSource.setMinEvictableIdleTimeMillis(datasourceConfiguration.getIdleTimeout() * 60 * 1000);
            }
            if (datasourceConfiguration.getBlockingTimeout() > 0) {
                druidDataSource.setMaxWait(datasourceConfiguration.getBlockingTimeout());
            }
            if ($.isNotNullAndNotEmpty(datasourceConfiguration.getConnectionInitSql())) {
                druidDataSource.setConnectionInitSqls(Arrays.asList(datasourceConfiguration.getConnectionInitSql()));
            }
            return druidDataSource;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static boolean checkDruidDataSource(DruidDataSource druidDataSource) {
        if (null == druidDataSource) {
            return false;
        }
        if ($.isNullOrEmpty(druidDataSource.getUrl())) {
            throw new NullPointerException("url");
        }
        if ($.isNullOrEmpty(druidDataSource.getUsername())) {
            throw new NullPointerException("username");
        }
        if ($.isNullOrEmpty(druidDataSource.getPassword())) {
            throw new NullPointerException("password");
        }
        if ($.isNullOrEmpty(druidDataSource.getDriverClassName())) {
            throw new NullPointerException("driver class id");
        }
        if (druidDataSource.getMinIdle() < 1) {
            log.error("[ds config check] min idle error size is:" + druidDataSource.getMinIdle());
            return false;
        }
        if (druidDataSource.getMaxActive() < 1) {
            log.error("[ds config check] max active error size is:" + druidDataSource.getMaxActive());
            return false;
        }
        if (druidDataSource.getMinIdle() > druidDataSource.getMaxActive()) {
            log.error("[ds config check] min pool size over max pool size min size is:" + druidDataSource.getMinIdle() + "max size is :" + druidDataSource.getMaxActive());
            return false;
        }
        return true;
    }

    /**
     * 初始化方法，创建对应的数据源，只能被调用一次
     */
    public void doInit() {
        // 1.初始化参数检查
        if ($.isNullOrEmpty(this.applicationId) || $.isNullOrEmpty(this.datasourceId)) {
            throw new NullPointerException(this.datasourceId + "applicationId or datasourceId");
        }
        // 3.获取全局配置
        String string = "";
        lock.lock();
        try {
            runTimeDatasourceConfiguration = DatasourceConfigurationParser.parseDatasourceConfiguration(string);
            DruidDataSource druidDataSource = createDruidDataSource(DatasourceHandler.this.datasourceId, this.runTimeDatasourceConfiguration);
            // 参数检查如果参数不正确直接抛出异常
            if (!checkDruidDataSource(druidDataSource)) {
                String errorMsg = "[ConfigError]init dataSource Prams Error! configuration is : " + druidDataSource.toString();
                log.error(errorMsg);
                throw new NullPointerException(errorMsg);
            }
            // 11.将创建好的数据源是指到TAtomDatasource中
            try {
                druidDataSource.init();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
            clearDataSourceWrapper();
            this.druidDataSource = druidDataSource;
        } finally {
            lock.unlock();
        }
    }

    public void clearDataSourceWrapper() {
        dataSourceWrapper = null;
    }


    public DataSourceWrapper getDataSourceWrapper() throws SQLException {
        if (dataSourceWrapper == null) {
            lock.lock();
            try {
                if (dataSourceWrapper != null) {
                    // 双检查锁
                    return dataSourceWrapper;
                }
                String errorMsg = "";
                if (null == druidDataSource) {
                    errorMsg = "[InitError] DatasourceHandler maybe forget init !";
                    log.error(errorMsg);
                    throw new SQLException(errorMsg);
                }
                DruidDataSource druidDataSource = this.druidDataSource;
                if (null == druidDataSource) {
                    errorMsg = "[InitError] DatasourceHandler maybe init fail !";
                    log.error(errorMsg);
                    throw new SQLException(errorMsg);
                }
                // 如果数据库状态不可用直接抛出异常
                if (null == this.getDatabaseStatus()) {
                    errorMsg = "[DB Stats Error] DatabaseStatus is Null: " + this.getDatasourceId();
                    log.error(errorMsg);
                    throw new SQLException(errorMsg);
                }
                DataSourceWrapper dataSourceWrapper = new DataSourceWrapper(applicationId, runTimeDatasourceConfiguration, druidDataSource);
                dataSourceWrapper.setDatasourceId(datasourceId);
                dataSourceWrapper.setIp(runTimeDatasourceConfiguration.getIp());
                dataSourceWrapper.setPort(runTimeDatasourceConfiguration.getPort());
                dataSourceWrapper.setDatabaseName(runTimeDatasourceConfiguration.getDatabaseName());
                dataSourceWrapper.setDatabaseStatus(getDatabaseStatus());
                log.info("set datasource key: " + datasourceId);
                dataSourceWrapper.init();
                this.dataSourceWrapper = dataSourceWrapper;
                return this.dataSourceWrapper;
            } finally {
                lock.unlock();
            }
        } else {
            return dataSourceWrapper;
        }
    }

    protected void doDestroy() {
        if (null != this.druidDataSource) {
            this.druidDataSource.close();

        }
    }

    public void destroyDataSource() {
        destroy();
    }

    public void setSingleInGroup(boolean isSingleInGroup) {
        this.runTimeDatasourceConfiguration.setSingleInGroup(isSingleInGroup);
    }


    public String getApplicationId() {
        return applicationId;
    }

    public void setApplicationId(String applicationId) {
        if (isInited()) {
            throw new IllegalArgumentException("[AlreadyInit] couldn't Reset applicationId !");
        }
        this.applicationId = applicationId;
    }

    public String getDatasourceId() {
        return datasourceId;
    }

    public void setDatasourceId(String datasourceId) {
        if (isInited()) {
            throw new IllegalArgumentException("[AlreadyInit] couldn't Reset datasourceId !");
        }
        this.datasourceId = datasourceId;
    }

    public DatabaseStatus getDatabaseStatus() {
        return this.runTimeDatasourceConfiguration.getDataBaseStatus();
    }

    public void setDatabaseStatusListenerList(List<DatabaseStatusListener> databaseStatusListenerList) {
        this.databaseStatusListenerList = databaseStatusListenerList;
    }

    public void handle(DatabaseStatus oldDatabaseStatus, DatabaseStatus newDatabaseStatus) {
        if (null != oldDatabaseStatus && oldDatabaseStatus != newDatabaseStatus) {
            if (null != databaseStatusListenerList) {
                for (DatabaseStatusListener databaseStatusListener : databaseStatusListenerList) {
                    try {
                        databaseStatusListener.handle(oldDatabaseStatus, newDatabaseStatus);
                    } catch (Exception e) {
                        log.error("[call StatusListenner Error] !", e);
                        continue;
                    }
                }
            }
        }
    }

    @Override
    public Logger log() {
        return log;
    }
}
